﻿/* Copyright © 2012 Gargol, Gargol at gbot dot org
   Based on CoreDC Copyright 2007 © Johan Pettersson, Fredrik Johansson, Michael Starberg, Christian Epstein.
 
This file is part of ADCCore.

ADCCore is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

ADCCore is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with ADCCore.  If not, see <http://www.gnu.org/licenses/>. */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ADCCore.Classes;
using ADCCore.Implementation;

namespace ADCCore
{
    public partial class DcClient
    {
        DcConnection connection;
        ProtocolHandler protocolHandler;
        private System.Windows.Forms.Timer pulseTimer;
        static readonly int pulseInterval = 120000;

        public bool IsConnected { get; set; }
        internal string Username { get; private set; }
        internal string Password { get; private set; }
        internal string SID { get;  set; }
        
        private void InitializeInstance()
        {
            protocolHandler = new ProtocolHandler(this);
        }

        /// <summary>
        /// Connect to a hub using values from the ClientInfo properties.
        /// </summary>
        public void Connect()
        {
            try
            {
                // Try connect to the hub, set up a listener for incomming messages
                // and finally set the username and password that will be used during this connection.
                IsConnected = false;
                nickList.Clear();
                DoConnecting();
                connection = new DcConnection(this);
                connection.OnMessage += connection_OnMessage;
                connection.Open(clientInfo.Hostname, clientInfo.Port);
                Username = clientInfo.Username;
                Password = clientInfo.Password;
                SendRawMessage("HSUP ADBASE ADTIGR ADALIV ADORLY");

                pulseTimer = new System.Windows.Forms.Timer();
                pulseTimer.Interval = pulseInterval;
                pulseTimer.Tick += pulseTimer_Tick;
                pulseTimer.Tag = "pulsTimer";
                pulseTimer.Start();
            }
            catch (Exception ex)
            {
                Exception res = TryReport(this, ex);
                if (clientInfo.ReconnectOnDisconnect)
                    connection.StartReconnect();
                if (res != null) throw;
            }
        }

        void connection_OnMessage(Object sender, string message)
        {
            if (System.Threading.Thread.CurrentThread.Name == null)
                System.Threading.Thread.CurrentThread.Name = "connection_OnMessage";
            // Recieves messages from a connection and forwards them to the protocolmanager
            // The protocol managager will later do a call back and trigger the proper event.
            //System.Threading.Thread workThread = new System.Threading.Thread(() => this.protocolHandler.ProcessCommand(message));
            //workThread.Start();
            
            this.protocolHandler.ProcessCommand(message);
        }
        void pulseTimer_Tick(object sender, EventArgs e)
        {
            try
            {
                //dcClient.SendAlive();
                if (System.Threading.Thread.CurrentThread.Name == null)
                {
                    System.Threading.Thread.CurrentThread.Name = "pulseTimer_Tick";
                }
                this.SendRawMessage("\n");
            }
            catch (System.IO.IOException)
            {
                // If we can't read from the stream, we've been disconnected
                Dispose();
                if (this.ClientInfo.ReconnectOnDisconnect)
                {
                    connection.StartReconnect();
                }
                this.DoDisconnected(this.ClientInfo.ReconnectOnDisconnect);
            }
            catch (Exception ex)
            {
                Dispose();
                if (this.ClientInfo.ReconnectOnDisconnect)
                {
                    connection.StartReconnect();
                }
                this.DoDisconnected(this.ClientInfo.ReconnectOnDisconnect);
                Console.WriteLine(ex.Message);
                // Something else might be wrong perhaps?
                //Exception res = dcClient.TryReport(dcClient, ex);
                //if (res != null) ;
            }
        }
        /// <summary>
        /// Disconnect from a hub.
        /// </summary>
        public void Disconnect()
        {
            try
            {
                connection.Dispose();
                IsConnected = false;
                nickList.Clear();
                DoDisconnected(false);
            }
            catch (Exception ex)
            {
                Exception res = TryReport(this, ex);
                if (res != null) throw;
            }
        }

        /// <summary>
        /// Sends a raw message to the hub. 
        /// If the message is missing a linefeed (\n), it will be added to the rawMessage.
        /// </summary>
        /// <param name="message">The raw message to send.</param>
        public void SendRawMessage(string message)
        {
            try
            {
                if (!message.EndsWith("\n"))
                {
                    message += "\n";
                }
                Console.WriteLine("SEND:> " + message);
                DoMessageSent(message);
                connection.Write(message);
            }
            catch (Exception ex)
            {
                if (clientInfo.ReconnectOnDisconnect)
                {
                    connection.StartReconnect();
                }
                Exception res = TryReport(this, ex);
                if (res != null) throw;
            }
        }

        /// <summary>
        /// Send a message to mainchat.
        /// </summary>
        /// <param name="message">The message to send.</param>
        public void SendMainchatMessage(string message)
        {
            if (message.EndsWith("\n"))
                message = message.Substring(0, message.Length - 1);
            message = String.Format("BMSG {0} {1}\n", SID, DcEncode(message));
            SendRawMessage(message);
        }

        /// <summary>
        /// Send Usercommand to a user.
        /// </summary>
        /// <param name="recipient">The SID of the recipient of the Usercommand</param>
        /// <param name="commandtext">The commandtext to send</param>
        public void SendADCUserCommands(string recipient, UserCommand[] UCs)
        {
            foreach (UserCommand uc in UCs)
            {
                string cmd = String.Format("{0}{1}\\n", uc.UserCmd, uc.HubCmd);
                SendRawMessage(String.Format("HRLY {0} ICMD {1} TT{2} CT2", recipient, uc.UserPath, cmd));
                //SendRawMessage(String.Format("HRLY {0} ICMD {1}",recipient,DcEncode(commandtext)));
            }
        }

        /// <summary>
        /// Send a private message to a user.
        /// </summary>
        /// <param name="recipient">The username of the recipient of the private message.</param>
        /// <param name="message">The message to send.</param>
        public void SendPrivateMessage(string username, string message)
        {
            User user = new User();
            List<User> list = this.nickList.Find(username);
            foreach (User u in list)
            {
                if (u.Username == username)
                    user = u;
            }
            SendPrivateMessage(user, message);

        }
        /// <summary>
        /// Send a private message to a user.
        /// </summary>
        /// <param name="recipient">The username of the recipient of the private message.</param>
        /// <param name="message">The message to send.</param>
        public void SendPrivateMessage(User recipient, string message)
        {
            if (message.EndsWith("\n"))
                message = message.Substring(0, message.Length - 1);
            string sendMessage = String.Format("EMSG {0} {1} {2} PM{0}\n", SID, recipient.SID, DcEncode(message));
            SendRawMessage(sendMessage);
            DoPrivateMessageSent(recipient, message);
        }


        /// <summary>
        ///  Send a private message to a user via mainchat. The hub must support the DMSG message.
        /// </summary>
        /// <param name="recipient">The user object for the recipient of the private mainchat message.</param>
        /// <param name="message">The message to send.</param>
        public void SendPrivateMainchatMessage(User recipient, string message)
        {
            message = String.Format("DMSG {0} {1} {2}\n", SID, recipient.SID, DcEncode(message));
            SendRawMessage(message);
        }
        public void SendPrivateMainchatMessage(string username, string message)
        {
            User recipient = new User();
            List<User> list = this.nickList.Find(username);
            foreach (User u in list)
            {
                if (u.Username == username)
                    recipient = u;
            }
            SendPrivateMainchatMessage(recipient, message);
        }
        /// <summary>
        /// As DcClient only supports passive mode connections this method performs 
        /// a passive search. Search replies are returned via the OnSearchReply event.
        /// </summary>
        /// <param name="searchInfo">The search to send to the hub.</param>
        public void SendSearch(SearchInfo searchInfo)
        {
            SendRawMessage(searchInfo.ToString());
        }

        /// <summary>
        /// Send a passive search reply to a specific recipient.
        /// </summary>
        /// <param name="recipient">The username for the recipient of the searchreply.</param>
        /// <param name="searchReply">The searchreply to send.</param>
        public void SendSearchReply(string recipient, SearchReply searchReply)
        {
            string message = String.Format("RES {0} {1} {2}\n", SID, recipient, searchReply.ToString());

            SendRawMessage(message);
        }

        /// <summary>
        /// Send a passive search reply to a specific recipient.
        /// </summary>
        /// <param name="recipient">The user object for the recipient of the searchreply.</param>
        /// <param name="searchReply">The searchreply to send.</param>
        public void SendSearchReply(User recipient, SearchReply searchReply)
        {
            SendSearchReply(recipient.SID, searchReply);
        }

        /// <summary>
        /// Answers Flexhub ILIV command with HLIV
        /// </summary>
        /// <param name="token">token from hub.</param>
        internal void SendAlive(String token)
        {
            var message = String.Format("HLIV {0} TO{1}", SID, token);
            SendRawMessage(message);
        }

        /// <summary>
        /// Requests that the remote user connects to our IP:port to open a filetransfer
        /// </summary>
        /// <param name="recipient">The user object for the recipient of the connectionrequest</param>
        public void SendConnectToMe(User recipient)
        {
            //CTM protocol separator port separator token
            var message = String.Format("DCTM {0} {1} {2}\n", recipient.SID, ClientInfo.ClientPort,recipient.Token);
            SendRawMessage(message);
        }

        /// <summary>
        /// Requests that the remote user sends a ConnectToMe to us, so we can connect to them to open a filetransfer
        /// </summary>
        /// <param name="recipient">The user object for the recipient of the requested connectionrequest</param>
        public void SendRevConnectToMe(User recipient,bool ssl)
        {
            //RCM protocol separator token
            string proto = "ADC/1.0";
            if (ssl)
                proto = "ADCS/0.10";
            var message = String.Format("DRCM {0} {1} {2} foobar\n", SID, recipient.SID,proto);
            SendRawMessage(message);
        }

        internal Exception TryReport(DcClient client, Exception ex)
        {
            if (client.DoError(ex))
            {
                return null;
            }

            return ex;
        }

        internal void Trace(string message)
        {

        }

        /// <summary>
        /// Encodes the text
        /// </summary>
        /// <param name="text">The text</param>
        /// <returns>The encoded text</returns>
        internal static string DcEncode(string text)
        {
            try
            {
                string strOutput = text;

                // Escape backslash
                strOutput = strOutput.Replace("\\", "\\\\");

                // Escape linefeed
                strOutput = strOutput.Replace("\n", "\\n");
                // Escape space
                System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex(@"[ ]{1,}", System.Text.RegularExpressions.RegexOptions.None);
                strOutput = regex.Replace(strOutput, @"\s");

                return strOutput;
            }
            catch (Exception ex)
            {
                return null;
            }
            /*
            //text = text.Replace("|", "&#124;");
            //text = text.Replace("$", "&#36;");
            text = text.Replace(" ", "\\s");
            text = text.Replace("\n", "\\n");
            return text;*/
        }

        /// <summary>
        /// Decodes the text
        /// </summary>
        /// <param name="text">The text</param>
        /// <returns>The decoded text</returns>
        internal static string DcDecode(string text)
        {
            text = text.Replace("\\\\", "\\");
            text = text.Replace("\\s", " ");
            text = text.Replace("\\n", Environment.NewLine);
            return text;
        }
    }
}
